﻿#region
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using StandardAnalyzer = Lucene.Net.Analysis.Standard.StandardAnalyzer;
using IndexWriter = Lucene.Net.Index.IndexWriter;
using FSDirectory = Lucene.Net.Store.FSDirectory;
using Version = Lucene.Net.Util.Version;
using Analyzer = Lucene.Net.Analysis.Analyzer;
using Document = Lucene.Net.Documents.Document;
using FilterIndexReader = Lucene.Net.Index.FilterIndexReader;
using IndexReader = Lucene.Net.Index.IndexReader;
using QueryParser = Lucene.Net.QueryParsers.QueryParser;
using Collector = Lucene.Net.Search.Collector;
using IndexSearcher = Lucene.Net.Search.IndexSearcher;
using Query = Lucene.Net.Search.Query;
using ScoreDoc = Lucene.Net.Search.ScoreDoc;
using Scorer = Lucene.Net.Search.Scorer;
using Searcher = Lucene.Net.Search.Searcher;
using TopScoreDocCollector = Lucene.Net.Search.TopScoreDocCollector;

using Lucene.Net.Documents;
using FisheryPlatform.Search.Entity;
using Lucene.Net.Search;
using Lucene.Net.Index;
using Lucene.Net.Util;
using FisheryPlatform.Common;
using FisheryPlatform.Search.Core.Analysis;
using System.Collections;
using Lucene.Net.QueryParsers;
using FisheryPlatform.Search.Core.IKAnalyzer;
using FisheryPlatform.Search.Core.PanGuAnalyzer;
using FisheryPlatform.SiteConfig;
using System.IO;
using Lucene.Net.Store;


#endregion
namespace FisheryPlatform.Search.Index
{
    public class IndexProduct : IndexData
    {
        System.IO.DirectoryInfo indexDir = new System.IO.DirectoryInfo(SiteConfigManager.AppSettings["Lucene.IndexDir"].Value);
        public void Analyze(List<ProductIndexEntity> productList)
        {
            #region
            try
            {
                var analyzer = GetAnalyzer();
                IndexWriter writer = new IndexWriter(FSDirectory.Open(indexDir), analyzer, true, IndexWriter.MaxFieldLength.LIMITED);
                //writer.SetMergeFactor();//合并因子
                //writer.SetMaxBufferedDocs();//最大缓存文档数
                //writer.SetMaxMergeDocs();//最大合并文档数
                #region 利用内存索引
                //RAMDirectory rmd = new RAMDirectory();
                //IndexWriter writer = new IndexWriter(rmd, new StandardAnalyzer(), true);
                //while (not eof)         //遍历
                //{
                //    Document doc = new Document();
                //doc.Add(…);  //Add Fields
                //writer.AddDocument(doc);
                //}
                //writer.SetUseCompoundFile(true);
                #endregion
                AddDataToDocs(writer, productList);
                writer.Optimize();
                writer.Close();
            }
            catch (System.IO.IOException e)
            {
                throw new Exception(" caught a " + e.GetType() + "\n with message: " + e.Message);
            }
            #endregion
        }

        private Analyzer GetAnalyzer()
        {
            #region
            if (1 == 2)
            {
                var analyzer = new MMAnalyzer();
                var filterDir = Path.Combine(SiteConfigManager.AppSettings["Lucene.DictDataDir"].Value, @"filters\");
                analyzer.FilterFactory = new FilterFactory(new Core.Resource.CentralPath(filterDir + "Reserved.txt"),
                                new Core.Resource.CentralPath(filterDir + "char.txt"),
                                new Core.Resource.CentralPath(filterDir + "Punctuation.txt"));
                var dictDir = Path.Combine(SiteConfigManager.AppSettings["Lucene.DictDataDir"].Value, @"dictionary\");
                analyzer.DictionaryFactory = new DictionaryFactory(new Core.Resource.CentralPath(dictDir));
                analyzer.StopWords = new Hashtable();

                return analyzer;
            }
            else
            {
                //PanGuTokenizer.InitPanGuSegment(@"E:\Project\SVNProjects\startprojects\Projects\FisheryPlatformSolution\FisheryPlatform.Web.WWW\Lucene\PanGu\PanGu.xml");
                //return new PanGuAnalyzer();
                return new IKAnalyzer(SiteConfigManager.AppSettings["Lucene.DictDataDir"].Value);
            }
            #endregion
        }

        private void AddDataToDocs(IndexWriter writer, List<ProductIndexEntity> productList)
        {
            #region
            productList.ForEach(proInfo =>
            {
                writer.AddDocument(GetDocument(proInfo));
            });
            #endregion
        }

        private Document GetDocument(ProductIndexEntity productInfo)
        {
            #region
            Document doc = new Document();
            //doc.SetBoost(); 权重越大越靠前

            doc.Add(new Field("ProductId", productInfo.ProductId.ToString(), Field.Store.YES, Field.Index.NOT_ANALYZED));
            doc.Add(new Field("CategoryId", productInfo.CategoryId.ToString(), Field.Store.YES, Field.Index.NOT_ANALYZED));
            doc.Add(new Field("AreaId", productInfo.AreaId.ToString(), Field.Store.YES, Field.Index.NOT_ANALYZED));
            var minPriceField = new NumericField("MinPrice", Field.Store.YES, true).SetDoubleValue(Convert.ToDouble(productInfo.MinPrice.ToString("F4")));
            doc.Add(minPriceField);
            var maxPriceField = new NumericField("MaxPrice", Field.Store.YES, true).SetDoubleValue(Convert.ToDouble(productInfo.MaxPrice.ToString("F4")));
            doc.Add(maxPriceField);
            doc.Add(new Field("Type", productInfo.Type.ToString(), Field.Store.YES, Field.Index.NOT_ANALYZED));
            doc.Add(new Field("UserId", productInfo.UserId.ToString(), Field.Store.YES, Field.Index.NOT_ANALYZED));

            doc.Add(new Field("Title", productInfo.Title, Field.Store.YES, Field.Index.ANALYZED));
            doc.Add(new Field("Description", productInfo.Description, Field.Store.NO, Field.Index.ANALYZED));

            var updateDateString = DateTools.DateToString(productInfo.UpdateDate, DateTools.Resolution.SECOND);
            doc.Add(new Field("UpdateDate", updateDateString, Field.Store.YES, Field.Index.NOT_ANALYZED));
            var createDateString = DateTools.DateToString(productInfo.CreateDate, DateTools.Resolution.SECOND);
            doc.Add(new Field("CreateDate", createDateString, Field.Store.YES, Field.Index.NOT_ANALYZED));

            return doc;
            #endregion
        }

        public ProductQueryResultEntity Search(ProductQueryEntity productQuery)
        {
            #region

            BooleanQuery queryConditionColl = AnalysisQuery(productQuery);
            IndexReader reader = IndexReader.Open(FSDirectory.Open(indexDir), true); // only searching, so read-only=true
            if (string.IsNullOrEmpty(productQuery.NormsField) == false)
                reader = new OneNormsReader(reader, productQuery.NormsField);

            Searcher searcher = new IndexSearcher(reader);
            Analyzer analyzer = GetAnalyzer(); //new PanGuAnalyzer(true);// new StandardAnalyzer(Version.LUCENE_CURRENT);
            //QueryParser parser = new QueryParser(Version.LUCENE_CURRENT, "", analyzer);
            //Query queryCondition = parser.Parse(productQuery.Description);

            var result = DoPagingSearch(searcher, queryConditionColl, productQuery.Paging, productQuery.PagingSize, productQuery.Raw, productQuery.Description == null);
            reader.Close();

            return result;
            #endregion
        }

        private BooleanQuery AnalysisQuery(ProductQueryEntity productQuery)
        {
            #region
            //Query query1 = new TermQuery(new Term(FieldValue, "name1")); // 词语搜索
            //Query query2 = new WildcardQuery(new Term(FieldName, "name*")); // 通配符 
            //Query query3 = new PrefixQuery(new Term(FieldName, "name1")); // 字段搜索 Field:Keyword，自动在结尾添加 *
            //Query query4 = new RangeQuery(new Term(FieldNumber, NumberTools.LongToString(11L)), new Term(FieldNumber, NumberTools.LongToString(13L)), true); // 范围搜索
            //Query query5 = new FilteredQuery(query, filter); // 带过滤条件的搜索

            BooleanQuery queryConditionColl = new BooleanQuery();
            if (productQuery.ProductId > 0)
            {
                queryConditionColl.Add(new TermQuery(new Term("ProductId", productQuery.ProductId.ToString())), BooleanClause.Occur.MUST);
            }
            else
            {
                if (productQuery.AreaId > 0)
                {
                    //queryCondition.Add(new TermQuery(new Term("", "")), BooleanClause.Occur.MUST);
                    queryConditionColl.Add(new TermQuery(new Term("AreaId", productQuery.AreaId.ToString())), BooleanClause.Occur.MUST);
                }

                if (productQuery.CategoryId > 0)
                {
                    queryConditionColl.Add(new TermQuery(new Term("CategoryId", productQuery.CategoryId.ToString())), BooleanClause.Occur.MUST);
                }

                if (productQuery.UserId > 0)
                {
                    queryConditionColl.Add(new TermQuery(new Term("UserId", productQuery.UserId.ToString())), BooleanClause.Occur.MUST);
                }

                if (productQuery.Type == 1 || productQuery.Type == 2)
                {
                    queryConditionColl.Add(new TermQuery(new Term("Type", productQuery.Type.ToString())), BooleanClause.Occur.MUST);
                }

                if (productQuery.MinPrice > 0)
                {
                    //new TermRangeQuery("MinPrice", productQuery.MinPrice.ToString("F3"), productQuery.MinPrice.ToString("F3"), true, true)
                    var nrq = NumericRangeQuery.NewDoubleRange("MinPrice", Convert.ToDouble(productQuery.MinPrice.ToString("F4")), productQuery.MaxPrice >= productQuery.MinPrice ? Convert.ToDouble(productQuery.MaxPrice.ToString("F4")) : double.MaxValue, true, false);
                    queryConditionColl.Add(nrq, BooleanClause.Occur.MUST);
                }

                if (productQuery.MaxPrice > 0 && productQuery.MaxPrice >= productQuery.MinPrice)
                {
                    var nrq = NumericRangeQuery.NewDoubleRange("MaxPrice", Convert.ToDouble(productQuery.MinPrice.ToString("F4")), Convert.ToDouble(productQuery.MaxPrice.ToString("F4")), false, true);
                    queryConditionColl.Add(nrq, BooleanClause.Occur.MUST);
                }

                if (string.IsNullOrEmpty(productQuery.Keyword) == false)
                {
                    //QueryParser queryParserTitle = new QueryParser(Version.LUCENE_CURRENT,"Title", new PanGuAnalyzer(true));
                    //Query query = queryParserTitle.Parse(productQuery.Keyword);
                    //queryConditionColl.Add(query, BooleanClause.Occur.SHOULD);

                    //QueryParser queryParserDescription = new QueryParser(Version.LUCENE_CURRENT, "Description", new PanGuAnalyzer(true));
                    //query = queryParserDescription.Parse(productQuery.Keyword);
                    //queryConditionColl.Add(query, BooleanClause.Occur.SHOULD);

                    //queryConditionColl.Add(MultiFieldQueryParser.Parse(Version.LUCENE_CURRENT, productQuery.Keyword, new string[] { "Title", "Descriptoin" }, new BooleanClause.Occur[] { BooleanClause.Occur.SHOULD }, GetChineseAnalyzer()), BooleanClause.Occur.MUST);
                    queryConditionColl.Add(new TermQuery(new Term("Title", productQuery.Keyword.Trim())), BooleanClause.Occur.SHOULD);
                    queryConditionColl.Add(new TermQuery(new Term("Description", productQuery.Keyword.Trim())), BooleanClause.Occur.SHOULD);
                }
            }
            if (queryConditionColl.Clauses().Count == 0)
            {
                MatchAllDocsQuery all = new MatchAllDocsQuery();
                //var searchAll = new WildcardQuery(new Term("ProductId", "*"));
                queryConditionColl.Add(all, BooleanClause.Occur.SHOULD);
            }
            return queryConditionColl;
            #endregion
        }

        private static ProductQueryResultEntity DoPagingSearch(Searcher searcher, Query query, int currentPageIndex, int hitsPerPage, bool raw, bool interactive)
        {
            #region
            ProductQueryResultEntity searchResult = new ProductQueryResultEntity();

            // Collect enough docs to show 5 pages
            TopScoreDocCollector collector = TopScoreDocCollector.create(currentPageIndex * hitsPerPage, false);
            searcher.Search(query, collector);
            ScoreDoc[] hits = collector.TopDocs().scoreDocs;

            int numTotalHits = collector.GetTotalHits();
            searchResult.TotalHits = numTotalHits;

            if ((currentPageIndex - 1) * hitsPerPage < numTotalHits)
            {
                searchResult.Index = (currentPageIndex - 1) * hitsPerPage;
            }
            else
            {
                //searchResult.List.Add("No such page");
            }
            searchResult.Count = System.Math.Min(numTotalHits, hitsPerPage);

            if (searchResult.Count > hits.Length)
            {
                #region
                collector = TopScoreDocCollector.create(numTotalHits, false);
                searcher.Search(query, collector);
                hits = collector.TopDocs().scoreDocs;
                #endregion
            }

            searchResult.Count = System.Math.Min(hits.Length, searchResult.Index + hitsPerPage);

            for (int i = searchResult.Index; i < searchResult.Count; i++)
            {
                #region
                if (raw)
                {
                    searchResult.RawList.Add("doc=" + hits[i].doc + " score=" + hits[i].score);
                    continue;
                }
                #endregion

                #region
                var proInfo = new ProductIndexEntity();
                Type proInfoType = proInfo.GetType();
                foreach (var propertyInfo in proInfoType.GetProperties())
                {
                    Document doc = searcher.Doc(hits[i].doc);
                    string fieldValue = doc.Get(propertyInfo.Name.Replace("get_", ""));
                    if (string.IsNullOrEmpty(fieldValue) == false)
                    {
                        if (propertyInfo.Name.EndsWith("Date"))
                        {
                            proInfoType.GetProperty(propertyInfo.Name.Replace("get_", "")).SetValue(proInfo, ConvertUtility.VariableValueTo(DateTools.StringToDate(fieldValue), propertyInfo.PropertyType.FullName), null);
                        }
                        else
                        {
                            proInfoType.GetProperty(propertyInfo.Name.Replace("get_", "")).SetValue(proInfo, ConvertUtility.VariableValueTo(fieldValue, propertyInfo.PropertyType.FullName), null);
                        }
                        //proInfoType.InvokeMember(memberInfo.Name, System.Reflection.BindingFlags.SetProperty, null, proInfo, new object[] { fieldValue });
                    }
                }
                searchResult.List.Add(proInfo);
                #endregion
            }
            return searchResult;
            #endregion
        }

        public bool DeleteIndex(string productId)
        {
            #region
            try
            {
                IndexReader reader = IndexReader.Open(FSDirectory.Open(indexDir), false);
                //reader.DeleteDocument();
                reader.DeleteDocuments(new Term("ProductId", productId));
                reader.Flush();
                reader.Close();
                
                var analyzer = GetAnalyzer();
                //注意在执行删除索引时IndexWriter中的Boolean属性必须为false；否则其他索引会被删除（true为重新建立索引，false为不重新建索引）
                IndexWriter writer = new IndexWriter(FSDirectory.Open(indexDir), analyzer, false, IndexWriter.MaxFieldLength.LIMITED);
                //writer.DeleteDocuments(
                writer.SetUseCompoundFile(true);
                writer.Optimize();
                writer.Close();
                return true;
            }
            catch (Exception ex)
            {
                throw new Exception("删除索引失败:" + productId + ex.ToString());
            }
            #endregion
        }

        public bool UpdateIndex(ProductIndexEntity productIndexInfo)
        {
            #region
            try
            {
                var analyzer = GetAnalyzer();
                //注意在执行删除索引时IndexWriter中的Boolean属性必须为false；否则其他索引会被删除（true为重新建立索引，false为不重新建索引）
                IndexWriter writer = new IndexWriter(FSDirectory.Open(indexDir), analyzer, false, IndexWriter.MaxFieldLength.LIMITED);

                writer.SetUseCompoundFile(true);
                var doc = GetDocument(productIndexInfo);
                writer.UpdateDocument(new Term("ProductId", productIndexInfo.ProductId.ToString()), doc);
                writer.Optimize();
                writer.Close();

                return true;
            }
            catch (Exception ex)
            {
                throw new Exception("更新索引失败:" + productIndexInfo.ProductId + ex.ToString());
            }
            #endregion
        }

    }
}
